import { DependencyContainer } from "tsyringe";
import { IDatabaseTables } from "@spt/models/spt/server/IDatabaseTables";
import { IPostDBLoadMod } from "@spt/models/external/IPostDBLoadMod";
import { IBotType } from "@spt/models/eft/common/tables/IBotType";
import { IPmcConfig } from "@spt/models/spt/config/IPmcConfig";
import { IBotConfig } from "@spt/models/spt/config/IBotConfig";
import { IWeatherConfig } from "@spt/models/spt/config/IWeatherConfig";
import { DatabaseServer } from "@spt/servers/DatabaseServer";
import { FileSystemSync } from "@spt/utils/FileSystemSync";
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
import { ConfigServer } from "@spt/servers/ConfigServer";
import { ILogger } from "@spt/models/spt/utils/ILogger";
import { jsonc } from "jsonc";

// DEMO: logger.info(`var to print: ${var_name}`);

function getattr(obj: any, prop: string) {
  return obj[prop];
}

class Mod implements IPostDBLoadMod
{
  private modPath = 'user/mods/SimpleTweaks';
  public botTable: Record<string, IBotType>;
  private config: Record<string, any>;
  public logger: ILogger;

  private difficulties: string[] = [
    "easy",
    "normal",
    "hard",
    "impossible"
  ];

  public postDBLoad(container: DependencyContainer): void
  {
    const fileSystem = container.resolve<FileSystemSync>("FileSystemSync");
    const configServer = container.resolve<ConfigServer>("ConfigServer");
    const databaseServer = container.resolve<DatabaseServer>("DatabaseServer");
    const logger = container.resolve<ILogger>("WinstonLogger");
    const tables: IDatabaseTables = databaseServer.getTables();
    const botTable = tables.bots.types;

    const pmcConfig: IPmcConfig = configServer.getConfig(ConfigTypes.PMC);
    const botConfig: IBotConfig = configServer.getConfig(ConfigTypes.BOT);
    const LODConfig: IBotConfig = configServer.getConfig(
      ConfigTypes.LOST_ON_DEATH);
    const weatherConfig: IWeatherConfig = configServer.getConfig(
      ConfigTypes.WEATHER);
    const inRaidConfig: IInRaidConfig = configServer.getConfig(
      ConfigTypes.IN_RAID);

    const defaultBrains = {
      "pmcbear": {
        "pmcBEAR": 1
      },
      "pmcusec": {
        "pmcUSEC": 1
      }
    };

    const playerScavBrains = {
      "assault": 1,
      "pmcBEAR": 1,
      "pmcUSEC": 1
    };

    let botTypes: string[] = [
      "arenafighter",
      "arenafighterevent",
      "assault",
      "assaultgroup",
      "bear",
      "bossboar",
      "bossboarsniper",
      "bossbully",
      "bossgluhar",
      "bosskilla",
      "bossknight",
      "bosskojaniy",
      "bosskolontay",
      "bosspartisan",
      "bosssanitar",
      "bosstagilla",
      "bosszryachiy",
      "crazyassaultevent",
      "cursedassault",
      "exusec",
      "followerbigpipe",
      "followerbirdeye",
      "followerboar",
      "followerboarclose1",
      "followerboarclose2",
      "followerbully",
      "followergluharassault",
      "followergluharscout",
      "followergluharsecurity",
      "followergluharsnipe",
      "followerkojaniy",
      "followerkolontayassault",
      "followerkolontaysecurity",
      "followersanitar",
      "followertagilla",
      "followerzryachiy",
      "gifter",
      "infectedtagilla",
      "marksman",
      "pmcbear",
      "pmcbot",
      "pmcusec",
      "ravangezryachiyevent",
      "sectantoni",
      "sectantpredvestnik",
      "sectantpriest",
      "sectantprizrak",
      "sectantwarrior",
      "usec",
    ];

    // read config file
    if (fileSystem.exists(`${this.modPath}/config/config.json`)) {
      this.config = jsonc.parse(fileSystem.read(
        `${this.modPath}/config/config.json`));
    } else {
      this.config = {
        "Enabled" : true,
        "Realtime": true,
        "RaidTime": 999,
        "RaidExtend": true,
        "FoodDamage": 0.5,
        "FixZombie" : true,
        "BotSense": 0.5,
        "BotPrecise": 2.0,
        "BotScatter": 2.0,
        "NoBossBrain": true,
        "NoTraitorScav": true,
        "NoScavCooldown": true,
        "NoFastReloads": true,
        "NoEternalStam": true,
        "NoSilentBots": true,
        "NoHeadAiming": true,
        "NoFIRHideout": true,
        "NoGrenades": true,
        "KeepGear": true
      };
      fileSystem.write(
        `${this.modPath}/config/config.json`,
        JSON.stringify(this.config, null,2));

      logger.info(
        `[SimpleTweaks]: config.json not found, creating default.`);
    }

    // disable whole mod
    if (!this.config.Enabled) {
      logger.info(`[SimpleTweaks]: Disabled`);
      return;
    }

    // disable 7x time accel
    if (this.config.Realtime) {
      if (weatherConfig?.acceleration) {
        weatherConfig.acceleration = 1;
      }
    }

    // disable traitor scavs
    if (this.config.NoTraitorScav) {
      inRaidConfig.playerScavHostileChancePercent = 0;
    }

    // fix zombie event
    if (this.config.FixZombie) {
      tables.bots.core["ACTIVE_HALLOWEEN_ZOMBIES_EVENT"] = true;
    }

    // remove scav cooldown time on local
    if (this.config.NoScavCooldown) {
      tables.globals.config.SavagePlayCooldown = 0;
      tables.globals.config.SavagePlayCooldownNdaFree = 0;
    }

    // disable hideout FIR requirement
    if (this.config.NoFIRHideout) {
      for (const area of tables.hideout.areas) {
        for (const stage of Object.values(area.stages)) {
          if (stage?.requirements) {
            for (const requirement of stage.requirements) {
              if (requirement?.isSpawnedInSession) {
                requirement.isSpawnedInSession = false;
              }
            }
          }
        }
      }
    }

    // reduce hydro & food damage
    tables.globals.config.Health.Effects.Existence.HydrationDamage *=
      this.config.FoodDamage;
    tables.globals.config.Health.Effects.Existence.EnergyDamage *=
      this.config.FoodDamage;

    // per location
    for (const location in tables.locations) {
      if (location === "base") continue;
      if (location === "hideout") continue;
      if (location === "develop") continue;
      if (location === "privatearea") continue;
      if (location === "suburbs") continue;
      if (location === "terminal") continue;
      if (location === "town") continue;

      // enable realtime game mode
      if (this.config.RaidTime !== 0) {

        var oldRaidTime = tables.locations[location].base.EscapeTimeLimitPVE;
        var newRaidTime = this.config.RaidTime;

        // change location raid time
        tables.locations[location].base.EscapeTimeLimit = newRaidTime;
        tables.locations[location].base.EscapeTimeLimitCoop = newRaidTime - 5;
        tables.locations[location].base.EscapeTimeLimitPVE = newRaidTime;
        tables.locations[location].base.AveragePlayTime = newRaidTime - 5;
        tables.locations[location].base.BotLocationModifier.MaxExfiltrationTime=
          (newRaidTime - 5) * 60;
        tables.locations[location].base.BotLocationModifier.MinExfiltrationTime=
          (newRaidTime - 10) * 60;

        if (this.config.RaidExtend) {
          if ((oldRaidTime != undefined) && (oldRaidTime < newRaidTime)) {
            for (var raidTime = 3; raidTime < newRaidTime; raidTime += 3){

              // adjust SCAV waves
              tables.locations[location].base.waves.push({
                "BotPreset": "easy",
                "BotSide": "Savage",
                "SpawnMode": [
                  "regular",
                  "pve"
                ],
                "SpawnPoints": "",
                "WildSpawnType": "assault",
                "isPlayers": false,
                "number": tables.locations[location].base.waves.length,
                "slots_max": 5,
                "slots_min": 1,
                "time_max": (raidTime + 3) * 60
                "time_min": (raidTime) * 60
              });

              tables.locations[location].base.waves.push({
                "BotPreset": "normal",
                "BotSide": "Savage",
                "SpawnMode": [
                  "regular",
                  "pve"
                ],
                "SpawnPoints": "",
                "WildSpawnType": "assault",
                "isPlayers": false,
                "number": tables.locations[location].base.waves.length,
                "slots_max": 3,
                "slots_min": 1,
                "time_max": (raidTime + 3) * 60
                "time_min": (raidTime) * 60
              });
            }
          }
        }
      }

      // sync bot max to config
      if (botConfig?.maxBotCap[location]) {
        botConfig.maxBotCap[location]=tables.locations[location].base.BotMaxPvE;
        if (botConfig.maxBotCap["default"] < botConfig.maxBotCap[location]) {
          botConfig.maxBotCap["default"] = botConfig.maxBotCap[location];
        }
      }
    }

    // remove boss brains
    if (this.config.NoBossBrain) {
      for (const pmcType in pmcConfig.pmcType) {
        for (const mapKey in pmcConfig.pmcType[pmcType]) {
          pmcConfig.pmcType[pmcType][mapKey] = defaultBrains[pmcType];
        }
      }
      for (const mapKey in botConfig.playerScavBrainType) {
        botConfig.playerScavBrainType[mapKey] = playerScavBrains;
      }
    }

    // per botType
    for (const botType of botTypes){
      // remove silent bots
      if (this.config.NoSilentBots) {
        if (botTable[botType].skills.Common?.BotSound) {
          let soundSkill = botTable[botType].skills.Common.BotSound;
          soundSkill.min = 0;
          soundSkill.max = 0;
        }
      }

      // no lightning reloads
      if (this.config.NoFastReloads) {
        if (botTable[botType].skills.Common?.BotReload) {
          let reloadSkill = botTable[botType].skills.Common.BotReload;
          reloadSkill.min = 0;
          reloadSkill.max = 0;
        }
      }

      // no eternal stamina
      if (this.config.NoEternalStam) {
        for (const diff of this.difficulties){
          let difficulty = getattr(botTable[botType].difficulty, diff)
          difficulty.Move.ETERNITY_STAMINA = false;
        }
      }

      // keep gear on death
      if (this.config.KeepGear) {
        LODConfig.equipment.Headwear = false;
        LODConfig.equipment.Earpiece = false;
        LODConfig.equipment.FaceCover = false;
        LODConfig.equipment.ArmorVest = false;
        LODConfig.equipment.Eyewear = false;
        LODConfig.equipment.TacticalVest = false;
        LODConfig.equipment.PocketItems = false;
        LODConfig.equipment.Backpack = false;
        LODConfig.equipment.Holster = false;
        LODConfig.equipment.FirstPrimaryWeapon = false;
        LODConfig.equipment.SecondPrimaryWeapon = false;
        LODConfig.questItems = false;
      }

      // per bot
      let bot = botTable[botType]
      if (bot) {
        for (const diff of this.difficulties) {
          let difficulty = getattr(bot.difficulty, diff);

          // reduce aiming precicing
          if (difficulty?.Aiming['MAX_AIM_TIME']) {
            difficulty.Aiming['MAX_AIM_TIME'] *=
               this.config.BotPrecise;
          }
          if (difficulty?.Aiming['MAX_AIM_PRECICING']) {
            difficulty.Aiming['MAX_AIM_PRECICING'] *=
               this.config.BotPrecise;
          }
          if (difficulty?.Aiming['SCATTERING_DIST_MODIF']) {
            difficulty.Aiming['SCATTERING_DIST_MODIF'] *=
               this.config.BotScatter;
          }
          if (difficulty?.Aiming['SCATTERING_DIST_MODIF_CLOSE']) {
            difficulty.Aiming['SCATTERING_DIST_MODIF_CLOSE'] *=
               this.config.BotScatter;
          }

          // reduce bot visibility & hearing
          if (difficulty?.Core["VisibleAngle"]) {
            // difficulty.Core["VisibleAngle"] *= this.config.BotSense;
          }
          if (difficulty?.Core['VisibleDistance']) {
            difficulty.Core['VisibleDistance'] *= this.config.BotSense;
          }
          if (difficulty?.Core["HearingSense"]) {
            difficulty.Core["HearingSense"] *= this.config.BotSense;
          }

          // no bot aim the head
          // 1: random + center
          // 2: random
          // 3: spot
          // 4: random + center + nohead
          // 5: random (no leg) + center + nohead
          // 6: head
          if (this.config.NoHeadAiming) {
            if (difficulty.Aiming['AIMING_TYPE'] > 5) {
              difficulty.Aiming['AIMING_TYPE'] = 5;
            }
          }

          // no grenades
          if (this.config.NoGrenades) {
            difficulty.Core['CanGrenade'] = false;
          }
        }
      }
    }
    logger.info(`[SimpleTweaks]: Enabled`);
  }
}

export const mod = new Mod();
